﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Windows.Forms;
using System.Reflection;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics;
using System.Drawing;

namespace IrisGenerator.Common.UI.XmlConfiguration
{
    internal abstract class XmlConfiguration
    {
        private static TraceSource ts = new TraceSource("XmlConfiguration Loader");

        static XmlConfiguration()
        {
            Trace.AutoFlush = true;
            ts.Switch.Level = SourceLevels.All;
            TraceListener listener = new XmlWriterTraceListener
            (@"C:\Documents and Settings\ldimitrijevic\Mes documents\Personnel\Developpement\Code Generator 2008\IrisGenerator\IrisGenerator.Common\Logs.xml");
            ts.Listeners.Add(listener);
        }

        public abstract void LoadConfiguration(XElement rootElement, Control ctrl);

        internal static object XmlToObject(XElement node,object Caller)
        {
            ts.TraceInformation("Entering XmlToObject");
            ts.TraceInformation("node value: " + node.Name.LocalName);
            

            if (node == null)
                return null;


            XAttribute att = node.Attribute("type");
            if (att == null || string.IsNullOrEmpty(att.Name.LocalName))
                return null;


            Type objectType = Type.GetType(att.Value);

            if (objectType == null)
                return null;

            object instance = Caller;
            
            if (instance == null)
                return null;

            ts.TraceInformation("Before call to FillObjectFromXmlAttribute");
            FillObjectFromXmlAttributes(node, instance);
            ts.TraceInformation("After call to FillObjectFromXmlAttribute");

            var dbQuery = from tmpNode in node.Descendants()
                          select tmpNode;

            foreach (var descendantNode in dbQuery)
            {
                ts.TraceInformation("New Child Node: " + descendantNode.ToString());
                ts.TraceInformation("Before GetChildrenNodesValue call");
                GetChildrenNodesValue(descendantNode, instance);
                ts.TraceInformation("After GetChildrenNodesValue call");
            }
            
            return null;
        }

        internal static void GetChildrenNodesValue(XElement node, object objectToFill)
        {
            //On chercher la propriété parmi l'objet à remplir...
            ts.TraceInformation("Entering GetChildrenNodesValue");
            ts.TraceInformation("node value: " + node.ToString());
            
            if (objectToFill==null)
            {
                ts.TraceInformation("objectToFill is null");
                return;
            }

            PropertyInfo pi = objectToFill.GetType().GetProperty(node.Name.LocalName);
            object instance =null;
            if (pi == null && node.Name.LocalName.Equals("Add"))
            {
                ts.TraceInformation("pi is null and node equals Add");
                instance = objectToFill;
            }
            else if (pi != null)
            {
                ts.TraceInformation("pi not null");
                instance = pi.GetValue(objectToFill, null);
            }
            else
                return;

            //Si la propritété vaut null, on crée un instance de cette propriété.
            if (instance == null)
            {
                ts.TraceInformation("instance is null. Creating Instance");
                instance=pi.PropertyType.InvokeMember("new",
                       BindingFlags.CreateInstance, null, instance,null);
                pi.SetValue(objectToFill, instance, null);
            }

            //On rempli l'instance de l'objet à l'aide des attributs du noeud.
            ts.TraceInformation("Calling FillObjectFromXmlAttributes");
            FillObjectFromXmlAttributes(node, instance);

            //Si l'objet implémente IList
            if (pi!=null && pi.PropertyType.GetInterface("IList") != null)
            {
                //Il s'agit d'une collection...
                //Il nous faut récupérer le type de base de la collection
                ts.TraceInformation("The property is a collection");
                Type collectionBaseType = GetCollectionParameterType(pi.PropertyType);
                

                //Si on a réussi à déterminer cette élément de base de la collection
                if (collectionBaseType != null)
                {
                    ts.TraceInformation("Base collection type: " + collectionBaseType.Name);

                    //Pour chaque noeud de type Add, on va ajouter un objet
                    //Puis on va l'analyser à son tour.
                    var dbQuery = from tmpNode in node.Descendants("Add")
                                  select tmpNode;

                    object colElementInstance=null;
                    foreach (XElement addNode in dbQuery)
                    {
                        //Création de l'instance de l'objet
                        ts.TraceInformation("New Add node found");
                        ts.TraceInformation("Creating Instance");

                        //Getting Constructor Parameters

                        object[] args = null;

                        var paramQuery = from paramNode in addNode.Descendants("Parameter")
                                         select paramNode;

                        if (paramQuery.Count() > 0)
                        {
                            args = new object[paramQuery.Count()];
                            int i = 0;

                            foreach (var param in paramQuery)
                            {
                                
                                string paramType = param.Attribute("Type").Value;
                                string paramValue = addNode.Attribute(param.Attribute("Value").Value).Value;

                                Type type = Type.GetType(param.Attribute("Type").Value);

                                if (type != null)
                                {
                                    args[i]=GetTypedValue(type,
                                        addNode.Attribute(param.Attribute("Value").Value).Value);
                                }
                                else
                                {
                                    args[i] = null;
                                }
                            }
                        }

                        colElementInstance = collectionBaseType.InvokeMember(
                            "new", BindingFlags.CreateInstance, null, colElementInstance,
                            args);

                        ts.TraceInformation("Calling GetChildrenNodesValue");
                        ts.TraceInformation("node: " + addNode.ToString());
                        ts.TraceInformation("colElementInstance", colElementInstance.ToString());
                        //Analyse de l'objet.
                        GetChildrenNodesValue(addNode, colElementInstance);

                        ts.TraceInformation("Adding element to collection");
                        //Ajout de l'objet à la collection.
                        instance.GetType().InvokeMember("Add",
                            BindingFlags.InvokeMethod, null, instance,
                            new object[] { colElementInstance });

                        
                    }
                }
            }
            else
            {
                ts.TraceInformation("Not a collection");
                foreach (XElement childNode in node.Elements())
                {
                    ts.TraceInformation("New node: " + childNode.ToString());
                    ts.TraceInformation("Instance: " + instance.ToString());
                    GetChildrenNodesValue(childNode, instance);
                }
            }
        }

        //internal static void AddCollectionItem(object collectionInstance,XElement itemNode,Type itemType)
        //{
        //    if (collectionInstance == null || itemNode == null || itemType == null)
        //        return;

        //    object itemInstance = itemType.InvokeMember("new",
        //        BindingFlags.CreateInstance, null, itemInstance, null);

        //    if (itemInstance == null)
        //        return;

            

        //}


        internal static Type GetCollectionParameterType(Type collectionType)
        {
            if (collectionType == null)
                return null;

            MemberInfo[] mi = collectionType.GetMember("Add");
            if (mi == null)
                return null;

            MethodInfo method;

            foreach (MemberInfo member in mi)
            {
                method = member as MethodInfo;
                if (method != null)
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    if (parameters != null && parameters.Length == 1)
                    {
                        return parameters[0].ParameterType;
                    }
                }
            }

            return null;
        }

        internal static void FillObjectFromXmlAttributes(XElement node, object objectToFill)
        {
            if (node == null || objectToFill == null)
                return;

            
            IEnumerable<XAttribute> attributes = node.Attributes();
            if (attributes == null)
                return;

            foreach (XAttribute att in attributes)
            {
                SetPropertyValue(att, objectToFill);
            }
        }

        internal static object GetTypedValue(Type objectType, string value)
        {
            object tmp=null;
            
            if (objectType.IsEnum)
            {
                tmp = Enum.Parse(objectType, value);
            }
            else
            {
                object[] converters = objectType.GetCustomAttributes(typeof(TypeConverterAttribute), true);
                if (converters != null && converters.Length > 0)
                {
                    foreach (TypeConverterAttribute converterAtt in converters)
                    {
                        TypeConverter converter = (TypeConverter)Type.GetType(converterAtt.ConverterTypeName)
                            .InvokeMember("new", BindingFlags.CreateInstance, null, null, new object[0]);

                        if (converter.CanConvertFrom(typeof(string)))
                        {
                            tmp= converter.ConvertFrom(value);
                        }
                    }
                }
                else
                {
                    try
                    {
                        tmp = Convert.ChangeType(value, objectType);
                    }
                    catch (NullReferenceException nullException)
                    {
                        throw;
                    }
                    catch (InvalidCastException castException)
                    {
                        throw;
                    }
                }

            }
            return tmp;
                

        }

        internal static void SetPropertyValue(XAttribute xmlAttribute, object objectToFill)
        {

            if (xmlAttribute == null || objectToFill == null)
                return;

            Type objType = objectToFill.GetType();
            PropertyInfo property = objType.GetProperty(xmlAttribute.Name.LocalName);

            if (property == null)
                return;

            Type objectType = property.PropertyType;

            if (objectType == null)
                return;

            try
            {
                property.SetValue(objectToFill, GetTypedValue(objectType, xmlAttribute.Value), null);
            }
            catch (Exception ex)
            {
                //TODO: Improve.
            }
            
        }
    }
}
